/* hal_efm.c */
#include "hal_efm.h"

/* customized in soc. */
#define EFM_UNLOCK_KEY_VALUE  0xfd9573f5

/* @brief Flash enable/disable flash write commands. */
//#define EFM_ENABLE_WE_COMMAND()   do { ARM_MPU_Disable();  } while (0)
//#define EFM_DISABLE_WE_COMMAND()  do { ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk); } while (0)

/* unlock for each launch cmd. */
static inline void EFM_UnlockCmdRegister(EFM_Type * base)
{
    base->CMD_UNLOCK = EFM_UNLOCK_KEY_VALUE;
}

void EFM_EnablePrefetch(EFM_Type * base, bool enable)
{
    if (enable)
    {
        base->CTRL |= EFM_CTRL_PREFETCH_EN_MASK;
    }
    else
    {
        base->CTRL &= ~EFM_CTRL_PREFETCH_EN_MASK;
    }
}
    
void EFM_SetWaitReadStateDiv(EFM_Type * base, uint32_t div)
{
    base->CTRL = (base->CTRL & ~EFM_CTRL_RWS_MASK) | EFM_CTRL_RWS(div);
}

uint32_t EFM_GetStatusFlags(EFM_Type * base)
{
    return base->STS;
}

void EFM_ClearStatusFlags(EFM_Type * base, uint32_t flags)
{
    base->STS = flags;
}

#if 0
void EFM_EnableInterrupts(EFM_Type * base, uint32_t flags)
{
    flags = flags & (0xF); /* only lower 4 bits can be used to setup interrupt in ctrl reg. */
    
    base->CTRL |= flags;
}

void EFM_DisableInterrupts(EFM_Type * base, uint32_t flags)
{
    flags = flags & (0xF); /* only lower 4 bits can be used to setup interrupt in ctrl reg. */
    
    base->CTRL &= ~flags;
}

#endif

typedef enum
{
	EFM_CmdCode_Program           = 0x02, /* 64 bit for M, 32 bit for L. */
	EFM_CmdCode_EraseSector       = 0x10,
	EFM_CmdCode_EraseBlock        = 0x12,
} EFM_CmdCode_Type;

typedef struct
{
    volatile EFM_CmdCode_Type CmdCode;
    volatile uint32_t *pAddr;
    volatile uint32_t *pData;
    volatile uint32_t WordSize;
} EFM_CmdParam_Type;

/* keep hte EFM_LaunchCmd() function in RAM, not in any flash device. */
START_FUNCTION_DECLARATION_RAMSECTION
uint32_t EFM_LaunchCmd(EFM_Type * base, EFM_CmdParam_Type * cmd_param) __attribute__((noinline))
END_FUNCTION_DECLARATION_RAMSECTION


START_FUNCTION_DEFINITION_RAMSECTION
DISABLE_CHECK_RAMSECTION_FUNCTION_CALL
uint32_t EFM_LaunchCmd(EFM_Type * base, EFM_CmdParam_Type * cmd_param)
{
    uint32_t err_code = STATUS_SUCCESS;
    
    /* Add NOP command to clear write buffer */
    base->CMD = 0;
    
    /* clear pending status */
    /* static inline functions are risks due to "-o0" optimization not inlined, so operate registers directly in ram function here. */
    base->STS = FEATURE_EFM_CMD_ERROR_MASK | EFM_STS_DONE_MASK;

    /* Passing parameter to the command */    
    //EFM_ENABLE_WE_COMMAND();
    for (uint32_t i = 0; i < cmd_param->WordSize; i++)
    {
        cmd_param->pAddr[i] = cmd_param->pData[i];
    }
    //EFM_DISABLE_WE_COMMAND();

    /* Write command register to launch command */
    //EFM_UNLOCK_CMD_REGISTER(base);
    base->CMD_UNLOCK = EFM_UNLOCK_KEY_VALUE; /* unlock the cmd reg. */
    base->CMD = EFM_CMD_CMD(cmd_param->CmdCode);
    
    while (EFM_STS_IDLE_MASK != (base->STS & EFM_STS_IDLE_MASK))
    {
        /* Wait till IDLE bit is set. */
    }
    
    if (0U != (base->STS & FEATURE_EFM_CMD_ERROR_MASK))
    {
        err_code = STATUS_ERROR;
    }
    
    return err_code;
}
ENABLE_CHECK_RAMSECTION_FUNCTION_CALL
END_FUNCTION_DEFINITION_RAMSECTION

/* dest_addr is in the range of address for dest block. 
* there are 3 blocks in ME05: pflash0, pflash1, dflash0.
*/

uint32_t EFM_EraseBlock(EFM_Type * base, uint32_t * paddr)
{
    uint32_t tmp32 = 0x12345678;
    EFM_CmdParam_Type cmd_param;

    cmd_param.CmdCode = EFM_CmdCode_EraseBlock; /* EFM_CmdCode_EraseBlockVerify. */
    cmd_param.pAddr = (uint32_t *)paddr;
    cmd_param.pData = &tmp32;
    cmd_param.WordSize = 1u;
    
    /* wait while the efm periph is busy. */
    while (0u == (EFM_FLAG_CMD_IDLE & EFM_GetStatusFlags(base)) )
    {
    }
    
    return EFM_LaunchCmd(base, &cmd_param);
}

/* dest_addr is in the range of address for dest sector.
* - #define FEATURE_EFM_MAIN_ARRAY_SECTOR_SIZE  (0x800u) // 2KB
* - #define FEATURE_EFM_DATA_ARRAY_SECTOR_SIZE  (0x400u) // 1KB
* - #define FEATURE_EFM_NVR_ARRAY_SECTOR_SIZE   (0x400u) // 1KB
*/
uint32_t EFM_EraseSector(EFM_Type * base, uint32_t * paddr)
{
    uint32_t tmp32 = 0x12345678;
    EFM_CmdParam_Type cmd_param;

    cmd_param.CmdCode = EFM_CmdCode_EraseSector; /* EFM_CmdCode_EraseSectorVerify. */
    cmd_param.pAddr = (uint32_t *)paddr;
    cmd_param.pData = &tmp32;
    cmd_param.WordSize = 1u;

    /* wait while the efm periph is busy. */
    while (0u == (EFM_FLAG_CMD_IDLE & EFM_GetStatusFlags(base)) )
    {
    }
    
    return EFM_LaunchCmd(base, &cmd_param);
}

/* pdata should be 4-byte (uint32_t) aligned.
 * each program operation write bytes counted by FEATURE_EFM_WRITE_UNIT_SIZE. 4 bytes by default.
 */
uint32_t EFM_Program(EFM_Type * base, uint32_t * paddr, const void * pdata)
{
    uint32_t tmp32 = 0x12345678;
    EFM_CmdParam_Type cmd_param;
    
    cmd_param.CmdCode = EFM_CmdCode_Program;
    cmd_param.pAddr = (uint32_t *)paddr;
    cmd_param.pData = (uint32_t *)pdata;
    cmd_param.WordSize = FEATURE_EFM_WRITE_UNIT_WORD_SIZE;

    /* wait while the efm periph is busy. */
    while (0u == (EFM_FLAG_CMD_IDLE & EFM_GetStatusFlags(base)) )
    {
    }

    return EFM_LaunchCmd(base, &cmd_param);
}

void EFM_Flush(EFM_Type * base)
{
    /* wait while the efm periph is busy. */
    while (0u == (EFM_FLAG_CMD_IDLE & EFM_GetStatusFlags(base)) )
    {
    }
}

/* EOF. */

